package game.client;

import util.ServerResponseThread;
import util.Messages;

import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.DatagramPacket;
import java.net.SocketTimeoutException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import game.util.GameLogger;
import game.controller.Unit.MilitaryUnit;
import game.controller.GameController;
import game.packet.GameAddUnitsPacket;
import game.packet.GameOps;
import game.packet.AllUnitsAddedPacket;
import lobby.packet.OpCode;

/**
 * AddUnitsResponseThread.java  Date Created: Aug 27, 2013
 *
 * Purpose: To send all addUnitPackets (AUP) to the server and wait for responses.
 *
 * Description: This thread will gather all AUPs and send them individually to the server.
 * Upon receiving confirmation the server was able to process each AUP the next AUP will be sent.
 *
 * @author Chrisb
 */
public class AddUnitsResponseThread extends ServerResponseThread {
    private static final int ADDUNITS_TIMEOUT = 5000;

    public AddUnitsResponseThread(InetAddress address, byte[] gameId, int nation, GameController controller) {
        try {
            this.address = InetAddress.getByName(Messages.getString("game.server.address"));
            socket = new MulticastSocket(2022);
            socket.setSoTimeout(ADDUNITS_TIMEOUT);
            ((MulticastSocket)socket).joinGroup(address);
            outgoingAUPs = new ArrayList<byte[]>();
            tempPackets = new ArrayList<byte[]>();
            this.controller = controller;
            this.gameId = gameId;
            this.nation = nation;
            failPacket = false;
        } catch (IOException e) {
            e.printStackTrace();
            GameLogger.log("AddUnitsResponseThread:constructor - " + e.getMessage());
        }
    }

    public void addPacket(byte[] gameId, int regionIndex, int regionType, ArrayList<MilitaryUnit> units) {
        outgoingAUPs.ensureCapacity(outgoingAUPs.size() + 1);
        outgoingAUPs.add(new GameAddUnitsPacket(gameId, regionIndex, regionType, units).getPacket());
    }

    public void clearAUPs() { outgoingAUPs.clear(); }
    public void sendPackets() { start(); }

    public void run() {
        Thread thisThread = Thread.currentThread();
        while (running == thisThread) {
            if (failPacket) {
                outgoingAUPs = tempPackets;
                tempPackets.clear();
                failPacket = false;
            }
            for (byte[] aup : outgoingAUPs) {
                outgoing = aup;
                outgoingPacket = new DatagramPacket(outgoing, outgoing.length, address, GAME_PORT);
                try {
                    socket.send(outgoingPacket);

                    incoming = new byte[256];
                    incomingPacket = new DatagramPacket(incoming, incoming.length);
                    socket.receive(incomingPacket);

                    if (incoming[0] == GameOps.ADD_UNITS.valueOf()) {
                        System.out.println("AddUnitsRT addUnits");
                        if (!Arrays.equals(outgoing, incoming)) {
                            //Fail different packet received.  For now ignore todo
                            System.err.println("AddUnitsRT: Failed to match incoming with outgoing package");
                        } else
                            System.out.println("AddUnitsRT: AUP added successfully");
                    } else if (incoming[0] == OpCode.FAILURE.valueOf()) {
                        //Packet returned fail packet.  Add original packet to temp array to resend.
                        failPacket = true;
                        tempPackets.add(aup);
                        System.err.println("AddUnitsRT: Fail addUnit Packet Received: " + incoming[0]);
                    } else {
                        //Unknown Packet Received
                        failPacket = true;
                        tempPackets.add(aup);
                        System.err.println("AddUnitsRT: Non-addUnit related Packet Received: " + incoming[0]);
                    }
                } catch (SocketTimeoutException e) {
                    onTimeout();
                } catch (IOException e) {             // would like to wait to hear from server that first packet was successful.
                    e.printStackTrace();
                    GameLogger.log("AddUnitsResponseThread:sendPackets - Failed to send packet - " + e.getMessage());
                }
            }
            System.out.println("AddUnitsRT: End of for-loop");
            if (!failPacket)
                sendUnitsAddedPacket();
        }
    }

    private void sendUnitsAddedPacket() {
        try {
            outgoing = new AllUnitsAddedPacket(gameId, nation).getPacket();
            outgoingPacket = new DatagramPacket(outgoing, outgoing.length, address, GAME_PORT);
            socket.send(outgoingPacket);

            incoming = new byte[256];
            incomingPacket = new DatagramPacket(incoming, incoming.length);
            socket.receive(incomingPacket);

            if (incoming[0] == GameOps.UNITS_ADDED.valueOf()) {
                controller.allUnitsAdded(new AllUnitsAddedPacket(incoming).getNation());
            }
        } catch (SocketTimeoutException e) {
            //Units Added timeout
            GameLogger.log("AddUnitsRT.UnitsAdded - timeout");
            sendUnitsAddedPacket();
        } catch (IOException e) {
            e.printStackTrace();
            GameLogger.log("AddUnitsRT.UnitsAddedPacket - " + e.getMessage());
        }
        System.out.println("AddUnitsRT - stopping thread");
        stopThread();
    }
    
    protected void onTimeout() {
        //todo
        //resending could just double the units..
        //if no response received from server:
        // 1. Server Crashed - No response will be received.
        // 2. Bad Ping - Will come in after timeout.
        // 3. Loss of internet - server may or may not have received the AUP.
    }

    private ArrayList<byte[]> outgoingAUPs;
    private ArrayList<byte[]> tempPackets;
    private GameController controller;
    private boolean failPacket;
    private byte[] gameId;
    private int nation;
}